﻿Namespace TransferEngine
    Friend Class FileShareDataChannel
        Implements Framework.Net.IDataChannel

        Private Class PartHolder
            Public Stream As IO.Stream
            Public Node As Framework.Net.Node
            Public StartOffset As Long
            Public BytesToSend As Integer
            Public BytesSent As Integer
            Public InReplyToMessageId As Integer
            Public LastBlockIndexSent As Integer
            Public TrackingId As String
        End Class

        Friend Shared Sub QueueWorkItem(ByVal workItem As TransmitPartWorkItem)
            SyncLock mUploadQueue
                Dim clsHolder As New PartHolder
                clsHolder.Node = workItem.Node
                clsHolder.StartOffset = workItem.PartId * TransferEngine.Manager.PartSize
                Dim clsFS As New IO.FileStream(workItem.FilePath, IO.FileMode.Open, IO.FileAccess.Read, IO.FileShare.Delete Or IO.FileShare.Read)
                clsHolder.Stream = clsFS
                clsHolder.Stream.Seek(clsHolder.StartOffset, IO.SeekOrigin.Begin)
                clsHolder.BytesToSend = CInt(Math.Min(TransferEngine.Manager.PartSize, clsHolder.Stream.Length - clsHolder.StartOffset))
                clsHolder.BytesSent = 0
                clsHolder.InReplyToMessageId = workItem.InReplyToMessageId
                clsHolder.LastBlockIndexSent = -1
                clsHolder.TrackingId = workItem.TrackingId
                mUploadQueue.Add(clsHolder)
            End SyncLock
        End Sub
        Friend Shared Sub CancelUploadPartRequest(ByVal node As Framework.Net.Node, ByVal trackingId As String)
            SyncLock mUploadQueue
                If mBlockBeingSent IsNot Nothing AndAlso mBlockBeingSent.Node Is node AndAlso mBlockBeingSent.TrackingId = trackingId Then
                    mUploadQueue.Remove(mBlockBeingSent)
                    mBlockBeingSent = Nothing
                Else
                    For Each clsH As PartHolder In mUploadQueue
                        If clsH.Node Is node And clsH.TrackingId = trackingId Then
                            mUploadQueue.Remove(clsH)
                            Exit For
                        End If
                    Next
                End If
            End SyncLock
        End Sub

        Private Shared mUploadQueue As New Generic.List(Of PartHolder)
        Private Shared mBlockBeingSent As PartHolder
        Private Shared mDownloadingPartsByInReplyToMessageId As New Generic.Dictionary(Of Integer, DownloadedPart)

        Private Sub BlockArrived(ByVal node As Framework.Net.Node, ByVal block As Framework.Net.IncomingDataChannelBlock) Implements Framework.Net.IDataChannel.BlockArrived
            SyncLock mUploadQueue
                Dim aReferenceId() As String = block.ReferenceId.Split("/"c)
                Dim iInReplyToMessageId = Integer.Parse(aReferenceId(0))
                Dim blnIsLast As Boolean = aReferenceId(1) = "1"
                Dim clsDlPart As DownloadedPart
                clsDlPart = mDownloadingPartsByInReplyToMessageId(iInReplyToMessageId)
                clsDlPart.Tag = aReferenceId(2)
                clsDlPart.Crc32CheckFailed = block.Crc32CheckFailed
                If blnIsLast Then
                    clsDlPart.Stream.Seek(0, IO.SeekOrigin.Begin)
                    mDownloadingPartsByInReplyToMessageId.Remove(iInReplyToMessageId)
                    DownloadQueue.QueueIncomingCompletedFilePart(clsDlPart)
                End If
            End SyncLock
        End Sub

        Private Function GetStreamForIncomingBlock(ByVal node As Framework.Net.Node, ByVal referenceId As String) As IO.Stream Implements Framework.Net.IDataChannel.GetStreamForIncomingBlock
            SyncLock mUploadQueue
                Dim aId() As String = referenceId.Split("/"c)
                Dim iInReplyToMessageId = Integer.Parse(aId(0))
                Dim clsID As DownloadedPart
                If mDownloadingPartsByInReplyToMessageId.ContainsKey(iInReplyToMessageId) Then
                    clsID = mDownloadingPartsByInReplyToMessageId(iInReplyToMessageId)
                Else
                    clsID = New DownloadedPart
                    clsID.Stream = New IO.MemoryStream
                    clsID.InReplyToMessageId = iInReplyToMessageId
                    clsID.Node = node
                    mDownloadingPartsByInReplyToMessageId.Add(iInReplyToMessageId, clsID)
                End If
                Return clsID.Stream
            End SyncLock
        End Function

        Private Sub BlockSent(ByVal node As Framework.Net.Node, ByVal block As Framework.Net.OutgoingDataChannelBlock) Implements Framework.Net.IDataChannel.BlockSent
            SyncLock mUploadQueue
                If mBlockBeingSent IsNot Nothing Then 'it can be null if request was cancelled
                    mBlockBeingSent.BytesSent += block.BytesToSend
                    If mBlockBeingSent.BytesToSend = mBlockBeingSent.BytesSent Then
                        mUploadQueue.Remove(mBlockBeingSent)
                        mBlockBeingSent.Stream.Close()
                        mBlockBeingSent.Stream = Nothing
                    End If
                    mBlockBeingSent = Nothing
                End If
            End SyncLock
        End Sub


        Private Function GetNextBlockToSend(ByVal node As Framework.Net.Node) As Framework.Net.OutgoingDataChannelBlock Implements Framework.Net.IDataChannel.GetNextBlockToSend
            SyncLock mUploadQueue
                If mUploadQueue.Count = 0 Then
                    mBlockBeingSent = Nothing
                    Return Nothing
                End If
                Dim clsBlock As PartHolder = mUploadQueue.Item(0)
                clsBlock.LastBlockIndexSent += 1

                Dim clsDB As New Framework.Net.OutgoingDataChannelBlock
                clsDB.BytesToSend = Math.Min(clsBlock.BytesToSend - clsBlock.BytesSent, 64 * 1024)
                clsDB.DataStream = clsBlock.Stream
                clsDB.TrackingId = clsBlock.TrackingId

                Dim iMsgId As Integer = clsBlock.InReplyToMessageId
                Dim strIsLastBlock As String = CStr(IIf(clsDB.BytesToSend + clsBlock.BytesSent = clsBlock.BytesToSend, "1", "0"))

                clsDB.ReferenceId = String.Format("{0}/{1}/{2}/{3}", iMsgId, strIsLastBlock, clsDB.Tag, clsBlock.LastBlockIndexSent)

                mBlockBeingSent = clsBlock
                Return clsDB
            End SyncLock
        End Function

        Private ReadOnly Property Name() As String Implements Framework.Net.IDataChannel.Name
            Get
                Return "FileTransfer"
            End Get
        End Property

        Private ReadOnly Property Priority() As Framework.Net.DataChannelPriority Implements Framework.Net.IDataChannel.Priority
            Get
                Return Framework.Net.DataChannelPriority.Normal
            End Get
        End Property

    End Class

    Friend Class DownloadedPart
        Public Node As Framework.Net.Node
        Public Stream As IO.Stream
        Public InReplyToMessageId As Integer
        Public Tag As String
        Public Crc32CheckFailed As Boolean
    End Class
End Namespace
